<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */
declare (strict_types=1);

namespace lflow\workflow;

use lflow\access\cache\memory\MemoryCache;
use lflow\access\DB\PDOHelper;
use lflow\access\Page;
use lflow\access\QueryFilter;
use lflow\entity\Process;
use lflow\helper\AssertHelper;
use lflow\helper\DateHelper;
use lflow\helper\facade\Cache;
use lflow\helper\ObjectHelper;
use lflow\helper\StringHelper;
use lflow\IProcessService;
use lflow\Logger;
use lflow\parser\ModelParser;
use lflow\WorkFlowException;

/**
 * 流程定义业务类
 *
 * @author Mr.April
 * @since  1.0
 */
class ProcessService extends AccessService implements IProcessService
{

    /**
     * 流程定义对象cache名称
     */
    private static string $CACHE_ENTITY = "workflow-engine:entity";

    /**
     * 流程id、name的cache名称
     */
    private static string $CACHE_NAME = "workflow-engine:name";

    public static string $PROCESS_UPDATE_CONTENT = "UPDATE wf_process SET content = ?";

    public function check(Process $process, string $idOrName): void
    {
        AssertHelper::notNull($process, "指定的流程定义[id/name=" . $idOrName . "]不存在");
        if ($process->getState() == 0) {
            throw new WorkFlowException("指定的流程定义[id/name=" . $idOrName .
                ",version=" . $process->getVersion() . "]为非活动状态");
        }
    }

    /**
     * 保存process实体对象
     *
     * @param \lflow\entity\Process $process
     */
    public function saveProcess(Process $process): void
    {
        $this->access()->saveProcess($process);
    }

    /**
     * 更新process实体对象
     *
     * @param \lflow\entity\Process $process
     */
    public function updateProcess(Process $process): void
    {
        $this->access()->updateProcess($process);
    }

    /**
     * 更新process的类别
     *
     * @param string $id
     * @param string $type
     */
    public function updateType(string $id, string $type): void
    {
        $this->access()->updateProcessType($id, $type);
    }

    /**
     * @param string $id
     *
     * @return \lflow\entity\Process
     * @throws \lflow\WorkFlowException
     */
    public function getProcessById(string $id): Process
    {
        AssertHelper::notEmpty($id);
        $cache  = Cache::handler();
        $entity = null;
        if (!empty($cache)) {
            $processName = $cache->get(self::$CACHE_NAME . ':' . $id);
            if (StringHelper::isNotEmpty($processName)) {
                $result = $cache->get(self::$CACHE_ENTITY . ':' . $processName);
                $entity = $result ? unserialize($result) : null;
            }
        }

        if (!empty($entity)) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[id={}] from cache." . $id);
            }
            return $entity;
        }
        $entity = $this->access()->getProcess($id);
        if ($entity) {
            $entity->setModel(ModelParser::parse($entity));
            $this->cache($entity);
        }
        return $entity;
    }

    public function getProcessByName(string $name): Process
    {
        return $this->getProcessByVersion($name, null);
    }

    public function getProcessByVersion($name, ?int $version): Process
    {
        AssertHelper::notEmpty($name);
        if ($version == null) {
            $version = $this->access()->getLatestProcessVersion($name);
        }
        if ($version == null) {
            $version = 0;
        }
        $entity      = null;
        $cache       = Cache::handler();
        $processName = $cache->get(self::$CACHE_NAME . ':' . $name);
        $entity      = $cache->get(self::$CACHE_ENTITY . ':' . $processName);
        if (!empty($entity)) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[name={}] from cache." . $processName);
            }
            return unserialize($entity);
        }
        $processs = $this->access()->getProcesss((new QueryFilter())->setName($name)->setVersion($version));
        if (!empty($processs)) {
            if (Logger::isDebugEnabled()) {
                Logger::debug("obtain process[name={}] from database.," . $processName);
            }
            $entity = PDOHelper::arrayToSimpleObj($processs[0], Process::class);
            $entity->setModel(ModelParser::parse($entity));
            $this->cache($entity);
        }
        return $entity;
    }

    /**
     * 根据流程定义json数据保存至数据库中，并且添加至缓存中
     *
     * @param array  $param   设计模型数据
     * @param string $creator 创建人uid
     *
     * @return string
     * @throws \lflow\WorkFlowException
     */
    public function deploy(array $param, string $creator): string
    {
        try {
            AssertHelper::notNull((object)$param);
            $entity = $this->access()->getProcess($param['id']);
            if ($entity->getVersion() > 0) {
                $entity->setId(StringHelper::getPrimaryKey());
                $entity->setVersion($entity->getVersion() + 1);
                $entity->setCreateTime(DateHelper::getTime());
                $entity->setCreator($creator);
                $entity->setContent(json_encode($param['content'], JSON_NUMERIC_CHECK) ?? json_encode([]));
                $this->access()->saveProcess($entity);
                $where = " WHERE id = ?";
                $args  = [
                    $entity->getContent(),
                    $entity->getId(),
                ];
                $this->access()->execute(self::$PROCESS_UPDATE_CONTENT . $where, $args);
                $this->cache($entity);
                return $entity->getId();
            }
            $entity->setVersion(1);
            $entity->setContent(json_encode($param['content'], JSON_NUMERIC_CHECK) ?? json_encode([]));
            $entity->setCreateTime(DateHelper::getTime());
            $entity->setCreator($creator);
            $this->access()->updateProcess($entity);
            $where = " WHERE id = ?";
            $args  = [
                $entity->getContent(),
                $entity->getId(),
            ];
            $this->access()->execute(self::$PROCESS_UPDATE_CONTENT . $where, $args);
            $this->cache($entity);
            return $entity->getId();
        } catch (\Exception $e) {
            Logger::error($e->getMessage());
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 根据流程定义id、json保存至数据库中，并且重新添加至缓存中影响当前运行实例 谨慎
     *
     * @param array  $param
     * @param string $creator
     *
     * @throws \lflow\WorkFlowException
     */
    public function redeploy(array $param, string $creator): void
    {
        try {
            $cache = Cache::handler();
            AssertHelper::notNull((object)$param);
            $entity = $this->access()->getProcess($param['id']);
            AssertHelper::notNull($entity);
            $entity->setModel(ModelParser::parse($entity));
            $this->access()->updateProcess($entity);
            $where = " WHERE id = ?";
            $args  = [
                $entity->getContent(),
                $entity->getId(),
            ];
            $this->access()->execute(self::$PROCESS_UPDATE_CONTENT . $where, $args);
            $cache->del(self::$CACHE_NAME . ':' . $entity->getId());
            $cache->del(self::$CACHE_ENTITY . ':' . $entity->getId() . '.' . $entity->getVersion());
            $this->cache($entity);
        } catch (\Exception $e) {
            Logger::error($e->getMessage());
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 卸载流程只会更新状态state值，不会物理删除数据。
     *
     * @param string $id
     * @param int    $state
     *
     * @throws \lflow\WorkFlowException
     */
    public function undeploy(string $id, int $state): void
    {
        try {
            $entity = $this->access()->getProcess($id);
            $entity->setState($state);
            $this->access()->updateProcess($entity);
            $this->cache($entity);
        } catch
        (\Exception $e) {
            Logger::error($e->getMessage());
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 级联删除指定流程定义的所有数据
     *
     * @param string $id
     */
    public function cascadeRemove(string $id): void
    {
        $entity        = $this->access()->getProcess($id);
        $historyOrders = $this->access()->getHistoryOrders((new QueryFilter())->setProcessId($id));
        $orderServer   = (new OrderService());
        foreach ($historyOrders as $historyOrder) {
            $orderServer->cascadeRemove($historyOrder['id']);
        }
        $this->access()->deleteProcess($entity);
    }

    /**
     *  查询流程定义
     *
     * @param \lflow\access\QueryFilter $filter
     * @param \lflow\access\Page|null   $page
     *
     * @return array
     * @throws \lflow\WorkFlowException
     */
    public function getProcesss(QueryFilter $filter, ?Page $page): array
    {
        AssertHelper::notNull($filter);
        return $this->access()->getProcesss($filter, $page);
    }

    public function getProcessJsonById(string $id): Process
    {
        AssertHelper::notEmpty($id);
        $DbEntity = $this->access()->getProcess($id);
        if ($DbEntity == null) {
            throw new WorkFlowException('未知流程');
        }
        return $DbEntity;
    }

    /**
     * 缓存实体
     *
     * @param \lflow\entity\Process $entity 流程定义对象
     *
     * @throws \lflow\WorkFlowException
     */
    private function cache(Process $entity): void
    {
        try {
            $cache = Cache::handler();
            if (empty($cache)) {
                if (Logger:: isDebugEnabled()) {
                    Logger:: debug("no cache implementation redis");
                }
            }
            if (empty($entity->getModel()) && !empty($entity->getContent())) {
                $entity->setModel(ModelParser::parse($entity));
            }
            $nameCache   = $cache->get(self::$CACHE_NAME . ':' . $entity->getId());
            $processName = $entity->getId() . '.' . $entity->getVersion();
            if (empty($nameCache)) {
                if (Logger::isDebugEnabled()) {
                    Logger::debug("cache process id is[{}],name is[{}]" . $entity->getId() . $entity->getVersion() . $processName);
                }
                $cache->set(self::$CACHE_NAME . ':' . $entity->getId(), $processName);
                $cache->set(self::$CACHE_ENTITY . ':' . $processName, serialize($entity));
            }
        } catch (WorkFlowException $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }
}

